import {
  type Accessor,
  type Component,
  Index,
  type ParentProps,
  type Ref,
  type Setter,
  Show,
  createEffect,
  createMemo,
  createRenderEffect,
  createSignal,
  mergeProps,
  on,
  splitProps,
} from "solid-js";
import { type NamedSlotTemplates, Slot, type Slots } from "@lib/Slot";
import "./index.scss";
import BootstrapIcon from "@lib/BootstrapIcon";
import type { JSX } from "solid-js/jsx-runtime";

export interface TextBoxDefaultSlotProps<TValue>
  extends JSX.InputHTMLAttributes<HTMLInputElement> {
  __self__?: {
    value?: Accessor<TValue | null>;
    setValue?: Setter<TValue | null>;
    handleKeyDown?: JSX.EventHandler<HTMLInputElement, KeyboardEvent>;
  };
}

export interface TextBoxClearButtonSlotProps
  extends Partial<JSX.HTMLAttributes<HTMLElement>> {
  ref?: any;
  showClear?: boolean;
}

export interface TextBoxSlots<TValue> extends NamedSlotTemplates {
  prefix?: Component<JSX.CustomAttributes<any>>;
  suffix?: Component<JSX.CustomAttributes<any> & { showSuffix?: boolean }>;
  prepend?: Component<JSX.CustomAttributes<any>>;
  append?: Component<JSX.CustomAttributes<any>>;
  default?: Component<TextBoxDefaultSlotProps<TValue>>;
  clear?: Component<TextBoxClearButtonSlotProps>;
}

export type Value<T> = T;

export interface TextBoxProps<TValue> extends Slots<TextBoxSlots<TValue>> {
  classList?: JSX.CustomAttributes<any>["classList"];
  style?: JSX.HTMLAttributes<HTMLElement>["style"];
  value?: TValue;
  onChange?: (value: TValue | null) => void;
  onEnter?: () => void;
  onEscape?: () => void;
  clearable?: boolean;
  readonly?: boolean;
  disabled?: boolean;
  placeholder?: string;
  onFocus?: () => void;
  onBlur?: () => void;
  type?: "text" | "number" | "password";
  onClick?: () => void;
  ref?: Ref<HTMLDivElement>;
}

export function triggerFocus(element?: HTMLElement, options?: FocusOptions) {
  if (!element) {
    return;
  }

  element.focus();
}

const InternalTextBox = <TValue extends any>(
  p: ParentProps<TextBoxProps<TValue>>
) => {
  const [local, props] = splitProps(
    mergeProps(
      {
        clearable: true,
        readonly: false,
        disabled: false,
        type: "text",
      },
      p
    ),
    ["classList", "slots", "children", "ref"]
  );

  const [_focused, _setFocused] = createSignal(false);
  const [_value, _setValue] = createSignal<TValue | null>(null);

  createRenderEffect(() => {
    _setValue((v) => props.value ?? null);
  });

  createEffect(
    on(_value, (v) => {
      // console.log("TextInput 值已变化", v);
      props.onChange?.(v);
    })
  );

  const hasValue = createMemo(() => {
    if (_value() === undefined || _value() === null) {
      return false;
    }

    if (Array.isArray(_value()) && (_value() as unknown[])?.length <= 0) {
      return false;
    }

    if (typeof _value() === "string") {
      return (_value() as string).trim() !== "";
    }

    return true;
  });

  const innerClassList = createMemo(() => {
    return {
      ...local.classList,
      "textBox-focused": _focused(),
      disabled: props.disabled,
    };
  });

  const handleInput: TextBoxDefaultSlotProps<TValue>["onInput"] = (e) => {
    _setValue(e.currentTarget.value as any);
  };

  const clearValue: TextBoxClearButtonSlotProps["onClick"] = () => {
    _setValue(null);
  };

  let ref!: HTMLDivElement;
  let inputRef!: HTMLInputElement;

  // const [_containerRef, _setContainerRef] = createSignal<
  //   HTMLDivElement | undefined
  // >(undefined);

  const [_inputRef, _setInputRef] = createSignal<HTMLInputElement | undefined>(
    undefined
  );

  const handleInputFocused: TextBoxDefaultSlotProps<TValue>["onfocus"] = (
    e
  ) => {
    _setFocused(true);
  };

  const handleInputBlured: TextBoxDefaultSlotProps<TValue>["onblur"] = (e) => {
    _setFocused(false);
    props.onBlur?.();
  };

  const handleInputKeyDown: TextBoxDefaultSlotProps<TValue>["onkeydown"] = (
    e
  ) => {
    if (e.code == "Enter") {
      props.onEnter?.();
    }
    if (e.code == "Escape") {
      props.onEscape?.();
    }
  };

  const handleClick: TextBoxDefaultSlotProps<TValue>["onClick"] = (e) => {
    props.onClick?.();
  };

  const defaultSlotContext: Accessor<TextBoxDefaultSlotProps<TValue>> =
    createMemo(() => {
      return {
        ref: (el) => _setInputRef(el),
        onInput: handleInput,
        onfocus: handleInputFocused,
        onblur: handleInputBlured,
        value: props.value as any,
        readonly: props.readonly,
        disabled: props.disabled,
        type: props.type,
        placeholder: props.placeholder,
        __self__: {
          value: _value,
          setValue: _setValue,
          handleKeyDown: handleInputKeyDown,
        },
        onKeyDown: handleInputKeyDown,
        onClick: handleClick,
      };
    });

  const [_mouseInControl, _setMouseInControl] = createSignal(false);

  const handleMouseEnter: JSX.HTMLAttributes<HTMLDivElement>["onmouseenter"] = (
    e
  ) => {
    _setMouseInControl(true);
  };

  const handleMouseLeave: JSX.HTMLAttributes<HTMLDivElement>["onmouseleave"] = (
    e
  ) => {
    _setMouseInControl(false);
  };

  const _showClear = createMemo(() => {
    return (
      (local.slots?.suffix === undefined ? true : _mouseInControl()) &&
      !props.readonly &&
      !props.disabled &&
      props.clearable &&
      hasValue()
    );
  });

  return (
    <div
      ref={local.ref}
      class={`textBox form-control `}
      classList={innerClassList()}
      style={props.style}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      <Show when={local.slots?.prepend}>
        <Slot slot={local.slots?.prepend}></Slot>
      </Show>
      <Show when={local.slots?.prefix}>
        <Slot slot={local.slots?.prefix}></Slot>
      </Show>
      <Slot slot={local.slots?.default} context={defaultSlotContext()}>
        {(ctx) => <input class="textBox-input" {...ctx}></input>}
      </Slot>
      <div style={{ "margin-left": 0 }}>
        <Slot
          slot={local.slots?.clear}
          context={{
            onClick: clearValue,
            children: "清空",
            onMouseDown: (e) => e.preventDefault(),
            showClear: _showClear(),
          }}
        >
          {TextBoxClearButton}
        </Slot>
        <Show when={local.slots?.suffix}>
          <Slot
            slot={local.slots?.suffix}
            context={{
              showSuffix: !_showClear(),
            }}
          ></Slot>
        </Show>
      </div>
      <Show when={local.slots?.append}>
        <Slot slot={local.slots?.append}></Slot>
      </Show>

      {local.children}
    </div>
  );
};

export const TextBoxClearButton = (
  p: ParentProps<TextBoxClearButtonSlotProps>
) => {
  const [local, props] = splitProps(p, ["showClear"]);

  return (
    <Show when={local.showClear}>
      <span
        class="textBox-clear-button align-middle badge badge-sm text-bg-light"
        {...props}
      ></span>
    </Show>
  );
};

export interface TextBoxComboInputDisplaySlots<TOption>
  extends NamedSlotTemplates {
  default: Component<{ option: TOption }>;
}

export interface TextBoxComboInputDisplayProps<TValue>
  extends Slots<TextBoxComboInputDisplaySlots<TValue>> {
  value?: TValue[];
  onRemove?: (value: TValue, index: number) => void;
}

export interface TextBoxComboInputSearchInputsProps<TValue>
  extends Omit<
    JSX.InputHTMLAttributes<HTMLInputElement>,
    "value" | "onChange"
  > {
  value?: TValue;
  onChange?: (value: TValue) => void;
  onKeyDown?: JSX.HTMLAttributes<HTMLInputElement>["onkeydown"];
}

export const TextBoxComboInputSearchInput = <TValue extends any>(
  p: TextBoxComboInputSearchInputsProps<TValue>
) => {
  const [local, props] = splitProps(p, ["value", "onChange", "onKeyDown"]);
  return (
    <input
      class="textBox-input"
      {...props}
      value={local.value as any}
      onInput={(e) => local.onChange?.(e.currentTarget.value as TValue)}
      onKeyDown={local.onKeyDown}
    ></input>
  );
};

export const TextBoxComboInputDisplay = <T extends {}>(
  p: TextBoxComboInputDisplayProps<T>
) => {
  const values = createMemo(() => {
    return p.value ?? [];
  });

  // createEffect(
  //   on(values, (v) => {
  //     console.log("values", v);
  //   })
  // );

  // createEffect(() => console.log("values", p.value));

  return (
    <Index each={values()}>
      {(_item, index) => (
        <span class="badge text-bg-light" style={{ "line-height": 1.5 }}>
          <Slot
            slot={p.slots?.default}
            context={{
              option: _item(),
            }}
          >
            {(ctx) => ctx.option as any}
          </Slot>

          <BootstrapIcon
            icon="x"
            class="ms-1"
            onclick={(e) => p.onRemove?.(_item(), index)}
            style={{ cursor: "pointer" }}
          ></BootstrapIcon>
        </span>
      )}
    </Index>
  );
};

export interface TextBoxComboInputSlots extends NamedSlotTemplates {
  display?: Component<TextBoxComboInputDisplayProps<any>>;
  input?: Component<TextBoxComboInputSearchInputsProps<any>>;
}

export const TextBoxComboInput = <TValue extends any>(
  p: TextBoxDefaultSlotProps<TValue[]> &
    Slots<TextBoxComboInputSlots> & {
      showSearch?: boolean;
    }
) => {
  const [control, props] = splitProps(mergeProps({ showSearch: true }, p), [
    "__self__",
    "onInput",
    "value",
    "slots",
    "showSearch",
  ]);

  const [_inputValue, _setInputValue] = createSignal<TValue | null>(null);

  const values = createMemo(() => {
    return (control.__self__?.value?.() ?? []) as TValue[];
  });

  const removeAtIndex = (index: number) => {
    if (index < 0) {
      return;
    }
    control.__self__?.setValue?.(
      (c: TValue[] | null) => c?.filter((p, i) => i !== index) ?? []
    );
  };

  const appendItem = () => {
    const currInput = _inputValue();
    if (currInput) {
      control.__self__?.setValue?.((c: TValue[] | null) => [
        ...(c ?? []),
        currInput,
      ]);
    }
    clearInput();
  };

  const clearInput = () => {
    _setInputValue(null);
  };

  const emptyInput = createMemo(() => {
    return typeof _inputValue() === "string"
      ? (_inputValue() as string).trim() === ""
      : _inputValue() === null ||
          _inputValue() === undefined ||
          _inputValue() === "";
  });

  const handleKeyDown: JSX.InputHTMLAttributes<HTMLInputElement>["onkeydown"] =
    (e) => {
      if (e.code === "Enter") {
        if (emptyInput()) {
          control.__self__?.handleKeyDown?.(e);
        } else {
          appendItem();
        }
      } else if (e.code === "Escape") {
        clearInput();
      } else if (e.code === "Backspace") {
        if (emptyInput()) {
          removeAtIndex(values().length - 1);
        }
      }
    };

  return (
    <>
      <div class="flex-grow-1">
        <div
          class="gap-1"
          style={{
            display: "flex",
            "flex-wrap": "wrap",
            "align-content": "space-between",
          }}
        >
          <Slot
            slot={control.slots?.display}
            context={{
              value: values(),
              onRemove: (v, i) => removeAtIndex(i),
            }}
          >
            {(ctx) => <TextBoxComboInputDisplay {...ctx} />}
          </Slot>
          <Show when={control.showSearch}>
            <Slot
              slot={control.slots?.input}
              context={{
                ...props,
                value: _inputValue(),
                onChange: _setInputValue,
                onKeyDown: handleKeyDown,
              }}
            >
              {(ctx) => (
                <TextBoxComboInputSearchInput
                  {...ctx}
                ></TextBoxComboInputSearchInput>
              )}
            </Slot>
          </Show>
        </div>

        {/* <input
          class="textBox-input me-1 mt-1"
          {...props}
          value={_inputValue() as any}
          onInput={(e) => _setInputValue(e.currentTarget.value as any)}
          onKeyDown={handleKeyDown}
        ></input> */}
      </div>
    </>
  );
};

type TextBoxType = typeof InternalTextBox & {
  ComboInput: typeof TextBoxComboInput & {
    Display: typeof TextBoxComboInputDisplay;
    Search: typeof TextBoxComboInputSearchInput;
  };
  ClearButton: typeof TextBoxClearButton;
};

const TextBox = InternalTextBox as TextBoxType;

TextBox.ComboInput = TextBoxComboInput as TextBoxType["ComboInput"];
TextBox.ComboInput.Display = TextBoxComboInputDisplay;
TextBox.ComboInput.Search = TextBoxComboInputSearchInput;
TextBox.ClearButton = TextBoxClearButton;

export default TextBox;
